<script setup>
import {onMounted, ref} from 'vue'
import {ElMessage, ElMessageBox, ElNotification} from 'element-plus'
import {Plus} from '@element-plus/icons-vue'
import {UploadFilled} from '@element-plus/icons-vue'
//sparkMD5包
import SparkMD5 from 'spark-md5'

import httpInstance from "@/http";

const chunkSize = 1024 * 1024 * 10; //10mb


const form = ref({
  name: "",
  file: [],
})

function computeMd5(file, uploadFile) {
  return new Promise((resolve, reject) => {

    //分片读取并计算md5
    const chunkTotal = 100; //分片数
    const chunkSize = Math.ceil(file.size / chunkTotal);
    const fileReader = new FileReader();
    const md5 = new SparkMD5();
    let index = 0;
    const loadFile = (uploadFile) => {
      uploadFile.parsePercentage.value = parseInt((index / file.size) * 100);
      const slice = file.slice(index, index + chunkSize);

      fileReader.readAsBinaryString(slice);
    };
    loadFile(uploadFile);
    fileReader.onload = (e) => {
      md5.appendBinary(e.target.result);
      if (index < file.size) {
        index += chunkSize;
        loadFile(uploadFile);
      } else {
        // md5.end() 就是文件md5码
        resolve(md5.end());
      }
    };
  });
}

//检查文件是否存在
const SUCCESS = '20000';

/**
 * 准备上传大文件，后端会返回一个文件id
 */
const initBigFile = async (fileInfo) => {
  const data = await httpInstance.post('/systemFile/initBigFile', fileInfo, {
    headers: {
      'Content-Type': 'application/json'
    },
  });
  //文件存在
  if (data.code === SUCCESS && data.data != null) {
    return data.data;
  }
  return null;
}


const fileList = ref([])

//这个方法必须要，不然报错
const uploadFile = (v) => {
}
//多文件上传必须使用的方法
const uploadChange = async (file, fileList) => {
  form.value.file = fileList;
};


const onSubmit = async () => {

  //数据交互
  let formData = new FormData();
  if (form.value.file.length === 0) {
    ElMessage.error("未选择文件！")
    return;
  }
  let file = form.value.file[0].raw;
  let uploadFile = {};
  uploadFile.name = file.name;
  uploadFile.size = file.size;
  uploadFile.parsePercentage = ref(0);
  uploadFile.uploadPercentage = ref(0);
  uploadFile.uploadSpeed = "0 M/s";
  uploadFile.chunkList = [];
  uploadFile.file = file;
  uploadFile.chunkTotal = Math.ceil(file.size / chunkSize);

  uploadFile.uploadingStop = false;
  let md5 = await computeMd5(file, uploadFile);//async 和 await配可以实现等待异步函数计算完成
  uploadFile.md5 = md5;
  let res = await initBigFile({
    'md5': md5,
    'name': uploadFile.name,
    'fileSize': uploadFile.size,
    'chunkSize': chunkSize,
    'chunkTotal': uploadFile.chunkTotal,
  });
  let fid = res.fid;
  if (fid === null) {
    ElMessage({
      message: '大文件上传失败',
      type: 'error',
    })
    return;
  }
  uploadFile.fid = fid;
  //开始上传分片
  await uploadChunk(file, 1, uploadFile);

  await mergeChunk(fid)
}

//上传文件分片
const uploadChunk = async (file, index, uploadFile) => {

  let chunkTotal = uploadFile.chunkTotal;
  let fid = uploadFile.fid;
  //传输完毕
  if (index > chunkTotal) {
    return;
  }
  // 根据是否暂停，确定是否继续上传
  let startTime = new Date().valueOf();
  let exit = uploadFile.chunkList.includes(index);
  //如果上传过这个分片
  if (exit) {
    //说明这个分片（第i个）上传过，那么百分比设为百分之i，然后进入下一个分片
    uploadFile.uploadPercentage = parseInt((uploadFile.chunkList.length / chunkTotal) * 100);
    await uploadChunk(file, index + 1, uploadFile);
  } else {

    //如果暂停了就不再继续传
    if (uploadFile.uploadingStop) {
      return;
    }
    // 分片上传，同时计算进度条和上传速度
    // 已经上传的不在上传、
    // 上传完成后提示，上传成功
    // console.log("上传分片1",index);
    let form = new FormData();
    let start = (index - 1) * chunkSize;
    let end =
        index * chunkSize >= file.size ? file.size : index * chunkSize;
    let chunk = file.slice(start, end);


    form.append("fid", fid);
    form.append("chunk", chunk);
    form.append("index", index);
    form.append("chunkTotal", chunkTotal);
    form.append("chunkSize", chunkSize);
    form.append("md5", uploadFile.md5);
    form.append("fileSize", file.size);
    form.append("name", file.name);

    const data = await httpInstance.post('/systemFile/uploadChunk', form, {
      //表单包含文件上传
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    });
    //分片上传失败
    if (data.code !== SUCCESS) {
      //todo:分片上传失败
      ElMessage({
        message: '分片上传失败',
        type: 'error',
      })
      return;
    }

    let endTime = new Date().valueOf();
    let timeDif = (endTime - startTime) / 1000;
    uploadFile.uploadSpeed = (10 / timeDif).toFixed(1) + " M/s";
    uploadFile.chunkList.push(index);
    uploadFile.uploadPercentage = parseInt((uploadFile.chunkList.length / chunkTotal) * 100);
// console.log("上传进度",uploadFile.uploadPercentage);

    //todo:注释这段意义不明的
//     if (index == chunkTotal) {
//       emitter.emit("reloadFileList");
//     }
    await uploadChunk(file, index + 1, uploadFile);
  }
}
const mergeChunk = async (fid) => {
  const data = await httpInstance.post('/systemFile/mergeChunk', {'fid': fid}, {
    headers: {
      'Content-Type': 'application/json'
    }
  });
  //文件存在
  if (data.code !== SUCCESS) {
    ElMessage({
      message: '大文件上传失败,文件未成功合并,服务器信息：' + data.msg,
      type: 'error',
    })
  }
}

let percentage = ref(0);
onMounted(() => {
  setInterval(() => {
    percentage.value = (percentage.value % 100) + 10
  }, 500)
})

const onSubmit2 = async () => {
  let form = new FormData();
  form.append('fid',37)
  const data= await httpInstance.post('/systemFile/mergeChunk', form, {
    headers: {
      'Content-Type': 'application/json'
    }
  });
  //文件存在
  if (data.code !== SUCCESS) {
    ElMessage({
      message: '大文件上传失败,文件未成功合并',
      type: 'error',
    })
  }

}

</script>


<template>

  <el-card style="max-width: 800px">
    <template #header>
      <div class="card-header">
        <span>大文件上传</span>
      </div>
    </template>

    <el-form :model="form" label-width="auto" style="max-width: 800px">

      <el-form-item label="文件名称">
        <el-input v-model="form.name"/>
      </el-form-item>

      <!-- 多文件上传要用到 uploadChange -->
      <el-form-item label="上传文件">
        <el-upload v-model:file-list="fileList" class="upload-demo" action="" drag :http-request="uploadFile"
                   limit="1"
                   :on-change="uploadChange"
                   :auto-upload="false"
        >
          <el-icon class="el-icon--upload">
            <upload-filled/>
          </el-icon>
          <div class="el-upload__text">
            Drop file here or <em>click to upload</em>
          </div>
        </el-upload>

      </el-form-item>

      <div class="demo-progress">
        <el-progress :percentage="percentage"/>
      </div>


      <el-form-item>
        <el-button type="primary" @click="onSubmit">Create</el-button>
        <el-button type="primary" @click="onSubmit2">Create2</el-button>
      </el-form-item>


    </el-form>

  </el-card>


</template>


<style lang="scss" scoped>
.demo-progress .el-progress--line {
  margin-bottom: 15px;
  max-width: 600px;
}
</style>